Skip to content

fix(quota): make InMemoryQuotaService constructor public so it resolves under DI#1330

Open
marcelo-maciel wants to merge 1 commit into
fullstackhero:mainfrom
marcelo-maciel:fix/quota-enabled-500-internal-ctor
Open

fix(quota): make InMemoryQuotaService constructor public so it resolves under DI#1330
marcelo-maciel wants to merge 1 commit into
fullstackhero:mainfrom
marcelo-maciel:fix/quota-enabled-500-internal-ctor

Conversation

@marcelo-maciel

Copy link
Copy Markdown
Contributor

Problem

Turning quota enforcement on brings the whole app down. With QuotaOptions.Enabled=true and no Redis configured, every authenticated request returns 500 — the login (/api/token) included. The "quota on" path ships disabled by default (QuotaOptions.Enabled=false in every appsettings) and appears never to have been exercised by a test, so the regression went unnoticed.

Root cause

AddHeroQuotas, when enabled without Redis, registers InMemoryQuotaService as the IQuotaService implementation. Its constructor was internal, and the default DI container only considers public constructors. QuotaEnforcementMiddleware resolves IQuotaService per request, so resolution threw:

InvalidOperationException: A suitable constructor for type
'FSH.Framework.Quota.InMemoryQuotaService' could not be located.

The host disables ValidateOnBuild, so this surfaced at request time rather than at startup — hence a running app that 500s on the first request through the pipeline. RedisQuotaService already had a public constructor, which is why only the in-memory (dev/test) path was affected.

Captured live via QuotaOptions__Enabled=true dotnet test ... --filter BillingDomainEdgeTests: the login POST /api/v1/identity/token/issue returned the exact exception above in the response body before the fix, and 6/6 pass after.

Fix

Make the InMemoryQuotaService constructor public, matching its sibling RedisQuotaService. One-word change; no behavioural change beyond making the type constructible by the container.

Test

Adds Framework.Tests/Quota/QuotaExtensionsTests that resolves IQuotaService from a scope (mirroring how the middleware resolves it) with quota both enabled and disabled. This pins the exact seam that broke — registration alone did not catch it because ValidateOnBuild is off. The enabled test reproduces the InvalidOperationException against the pre-fix code and passes after.

Verification

  • dotnet test src/Tests/Framework.Tests — 114/114 pass.
  • QuotaOptions__Enabled=true dotnet test ... --filter BillingDomainEdgeTests — 6/6 pass (2 failed before the fix).

Out of scope (separate follow-up)

StorageBytes overage is hard-capped: CheckAndRecordAsync rolls back past the limit and QuotaMeteredStorageService throws 507, so Used never exceeds Limit and UsageSnapshot.Overage stays 0 — storage overage never bills. A soft-cap mode (allow past the limit + record, so MonthlyInvoiceJob can bill Used - Limit) is worth a separate issue/PR.

…es under DI

With QuotaOptions.Enabled=true and no Redis configured, AddHeroQuotas registers
InMemoryQuotaService as the IQuotaService. Its constructor was internal, and the
default DI container only considers public constructors, so QuotaEnforcementMiddleware
could not resolve the service per request -- turning every authenticated request, the
login included, into a 500 ("A suitable constructor for type '...InMemoryQuotaService'
could not be located"). The host disables ValidateOnBuild, so the failure surfaced at
request time instead of startup. RedisQuotaService already had a public constructor,
which is why only the in-memory (dev/test) path was affected.

Add Framework.Tests coverage that resolves IQuotaService from a scope with quota both
enabled and disabled, guarding the exact seam that broke.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant